package com.yesido.javabase.xuehua;

/**
 * @author yesido
 * @description
 * @date 2023-06-28 17:26
 * @Version 8.3
 */
public class SnowflakeIdGenerator {
    // 起始的时间戳，可以根据实际情况进行配置
    private final long START_TIMESTAMP = 1630454400000L;

    // 每部分占用的位数
    private final long SEQUENCE_BITS = 12;   // 序列号占用的位数
    private final long NODE_ID_BITS = 10;    // 节点ID占用的位数

    // 每部分的最大值
    private final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BITS);
    private final long MAX_NODE_ID = -1L ^ (-1L << NODE_ID_BITS);

    // 每部分向左的位移
    private final long NODE_ID_SHIFT = SEQUENCE_BITS;
    private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + NODE_ID_BITS;

    // 当前节点的节点ID，可以根据实际情况进行配置
    private long nodeId;

    // 上一次生成ID时的时间戳和序列号
    private long lastTimestamp = -1L;
    private long sequence = 0L;

    public SnowflakeIdGenerator(long nodeId) {
        if (nodeId < 0 || nodeId > MAX_NODE_ID) {
            throw new IllegalArgumentException("Invalid nodeId");
        }
        this.nodeId = nodeId;
    }

    public synchronized long generateId() {
        long currentTimestamp = System.currentTimeMillis();

        // 如果当前时间小于上次生成ID的时间戳，说明系统时钟回退过，需要等待时钟追上
        if (currentTimestamp < lastTimestamp) {
            try {
                wait(lastTimestamp - currentTimestamp);
            } catch (InterruptedException e) {
                throw new RuntimeException("Failed to generate ID", e);
            }
            currentTimestamp = System.currentTimeMillis();
            if (currentTimestamp < lastTimestamp) {
                throw new RuntimeException("Clock is moving backwards");
            }
        }

        // 如果是同一毫秒内生成ID，递增序列号；否则，序列号重置为0
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                // 序列号达到最大值时，等待下一毫秒再生成ID
                currentTimestamp = nextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = currentTimestamp;

        // 生成ID
        long id = (currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT |
                nodeId << NODE_ID_SHIFT |
                sequence;

        return id;
    }

    private long nextMillis(long lastTimestamp) {
        long currentTimestamp = System.currentTimeMillis();
        while (currentTimestamp <= lastTimestamp) {
            currentTimestamp = System.currentTimeMillis();
        }
        return currentTimestamp;
    }
}

